﻿using System;
using System.IO;
using System.Net;
using System.Security;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Resources;

namespace Gmantis.Imaging
{
    public enum InterpolationMode
    {
        Default,
        Fast,
        HighQuality
    }

    public enum ImageFormat
    {
        Unknown,
        Png,
        Jpg,
        Gif
    }

    public class Bitmap
    {
        // Fields
        private byte[] _buffer;
        private bool _dirty;
        private AnimationFrameCollection _frames;
        private int _height;
        private BitmapImage _imgSource;
        private InterpolationMode _interpolationMode;
        private int _lockUpdate;
        internal const int _rowExtra = 6;
        private int _rowLength;
        private Uri _uriSource;
        private int _width;

        // Methods
        public Bitmap()
            : this(0, 0)
        {
        }

        public Bitmap(Bitmap bmp)
        {
            this.Copy(bmp, false);
        }

        public Bitmap(Stream stream)
        {
            this.SetStream(stream);
        }

        public Bitmap(Uri uriSource)
        {
            this.UriSource = uriSource;
        }

        public Bitmap(WriteableBitmap writeableBitmap)
        {
            this.Load(writeableBitmap);
        }

        public Bitmap(int width, int height)
        {
            this.SetSize(width, height);
        }

        public Bitmap(Bitmap bmp, int width, int height)
        {
            this.SetSize(width, height);
            this.Copy(bmp, true);
        }

        public void BeginUpdate()
        {
            this._lockUpdate++;
        }

        public void Copy(Bitmap source, bool resize)
        {
            if (!resize || ((source.Width == this.Width) && (source.Height == this.Height)))
            {
                this._rowLength = source._rowLength;
                this._width = source._width;
                this._height = source._height;
                if ((this._buffer == null) || (this._buffer.Length != source._buffer.Length))
                {
                    this._buffer = new byte[source._buffer.Length];
                }
                Buffer.BlockCopy(source._buffer, 0, this._buffer, 0, this._buffer.Length);
            }
            else
            {
                this.GetImageResizeFilter().Resize(source, this);
            }
            this.Invalidate();
        }

        public void DrawBitmap(Bitmap bitmap, int left, int top)
        {
            this.BeginUpdate();
            for (int i = 0; i < bitmap.Height; i++)
            {
                for (int j = 0; j < bitmap.Width; j++)
                {
                    int col = j + left;
                    int row = i + top;
                    if ((col < this.Width) && (row < this.Height))
                    {
                        Color pixel = bitmap.GetPixel(j, i);
                        if (pixel.A != 0)
                        {
                            this.SetPixel(col, row, pixel);
                        }
                    }
                }
            }
            this.EndUpdate();
        }

        public void EndUpdate()
        {
            this._lockUpdate = Math.Max(0, this._lockUpdate - 1);
            if ((this._lockUpdate <= 0) && this._dirty)
            {
                this.Invalidate();
            }
        }

        internal byte[] GetImageData()
        {
            this._dirty = true;
            return this._buffer;
        }

        public static ImageFormat GetImageFormat(Stream stream)
        {
            try
            {
                stream.Position = 0L;
                byte[] buffer = new byte[8];
                stream.Read(buffer, 0, buffer.Length);
                stream.Position = 0L;
                if (Match(buffer, PngCodec.Signature))
                {
                    return ImageFormat.Png;
                }
                if (Match(buffer, GifCodec.Signature))
                {
                    return ImageFormat.Gif;
                }
                if (Match(buffer, JpgCodec.Signature))
                {
                    return ImageFormat.Jpg;
                }
                return ImageFormat.Unknown;
            }
            catch (Exception)
            {
                return ImageFormat.Unknown;
            }
        }

        private ImageResizeFilter GetImageResizeFilter()
        {
            switch (this._interpolationMode)
            {
                case InterpolationMode.Fast:
                    return new ImageResizeFilterBox();

                case InterpolationMode.HighQuality:
                    return new ImageResizeFilterMitchell();
            }
            return new ImageResizeFilterTriangle();
        }

        internal BitmapImage GetImageSource()
        {
            return this._imgSource;
        }

        public Color GetInterpolatedPixel(double x, double y)
        {
            int col = Math.Max(0, Math.Min((int)Math.Floor(x), this.Width - 1));
            int row = Math.Max(0, Math.Min((int)Math.Floor(y), this.Height - 1));
            int pixelIndex = this.GetPixelIndex(col, row);
            int index = this.GetPixelIndex(Math.Min((int)(col + 1), (int)(this.Width - 1)), row);
            int num5 = this.GetPixelIndex(col, Math.Min((int)(row + 1), (int)(this.Height - 1)));
            int num6 = this.GetPixelIndex(Math.Min((int)(col + 1), (int)(this.Width - 1)), Math.Min((int)(row + 1), (int)(this.Height - 1)));
            double dx = x - Math.Floor(x);
            double dy = y - Math.Floor(y);
            double dxdy = dx * dy;
            Func<byte, byte, byte, byte, byte> func = (ctl, ctr, cbl, cbr) => (byte)((((ctl * (((1.0 - dx) - dy) + dxdy)) + (ctr * (dx - dxdy))) + (cbl * (dy - dxdy))) + (cbr * dxdy));
            return Color.FromArgb(func(this._buffer[pixelIndex + 3], this._buffer[index + 3], this._buffer[num5 + 3], this._buffer[num6 + 3]), func(this._buffer[pixelIndex], this._buffer[index], this._buffer[num5], this._buffer[num6]), func(this._buffer[pixelIndex + 1], this._buffer[index + 1], this._buffer[num5 + 1], this._buffer[num6 + 1]), func(this._buffer[pixelIndex + 2], this._buffer[index + 2], this._buffer[num5 + 2], this._buffer[num6 + 2]));
        }

        public Color GetPixel(int col, int row)
        {
            if ((col < 0) || (col >= this._width))
            {
                throw new IndexOutOfRangeException("Column must be greater than 0 and less than the Width");
            }
            if ((row < 0) || (row >= this._height))
            {
                throw new IndexOutOfRangeException("Row must be greater than 0 and less than the Height");
            }
            int pixelIndex = this.GetPixelIndex(col, row);
            return Color.FromArgb(this._buffer[pixelIndex + 3], this._buffer[pixelIndex], this._buffer[pixelIndex + 1], this._buffer[pixelIndex + 2]);
        }

        internal int GetPixelIndex(int col, int row)
        {
            return (((this._rowLength * row) + (col << 2)) + 6);
        }

        public Stream GetStream()
        {
            return this.GetStream(ImageFormat.Png, true);
        }

        public Stream GetStream(bool compress)
        {
            return this.GetStream(ImageFormat.Png, compress);
        }

        public Stream GetStream(ImageFormat format, bool compress)
        {
            switch (format)
            {
                case ImageFormat.Png:
                    return PngCodec.Encode(this, compress);

                case ImageFormat.Jpg:
                    return JpgCodec.Encode(this);

                case ImageFormat.Gif:
                    throw new NotImplementedException("GIF encoder is not implemented.");
            }
            throw new BadImageFormatException("Image format not recognized.");
        }

        private void Invalidate()
        {
            this._dirty = true;
            if ((((this._lockUpdate == 0) && (this._imgSource != null)) && ((this._buffer != null) && (this._buffer.Length > 0))) && ((this.Width > 0) && (this.Height > 0)))
            {
                this._imgSource.SetSource(PngCodec.Encode(this, false, false));
                this._dirty = false;
                if (this._frames != null)
                {
                    this._frames.BeginAnimation();
                }
            }
        }

        private void Load(WriteableBitmap writeableBitmap)
        {
            this.SetSize(writeableBitmap.PixelWidth, writeableBitmap.PixelHeight);
            this.BeginUpdate();
            for (int i = 0; i < this.Height; i++)
            {
                for (int j = 0; j < this.Width; j++)
                {
                    int num3 = writeableBitmap.Pixels[(i * this.Width) + j];
                    this.SetPixel(j, i, (byte)(num3 >> 0x10), (byte)(num3 >> 8), (byte)num3, (byte)(num3 >> 0x18));
                }
            }
            this.EndUpdate();
        }

        internal static bool Match(byte[] b1, byte[] b2)
        {
            for (int i = 0; (i < b1.Length) && (i < b2.Length); i++)
            {
                if (b1[i] != b2[i])
                {
                    return false;
                }
            }
            return true;
        }

        public void SetPixel(int col, int row, Color color)
        {
            this.SetPixel(col, row, color.R, color.G, color.B, color.A);
        }

        public void SetPixel(int col, int row, byte red, byte green, byte blue, byte alpha)
        {
            if ((col < 0) || (col >= this._width))
            {
                throw new IndexOutOfRangeException("Column must be greater than 0 and less than the Width");
            }
            if ((row < 0) || (row >= this._height))
            {
                throw new IndexOutOfRangeException("Row must be greater than 0 and less than the Height");
            }
            int pixelIndex = this.GetPixelIndex(col, row);
            this._buffer[pixelIndex] = red;
            this._buffer[pixelIndex + 1] = green;
            this._buffer[pixelIndex + 2] = blue;
            this._buffer[pixelIndex + 3] = alpha;
            this.Invalidate();
        }

        public void SetSize(int width, int height)
        {
            if (this._frames != null)
            {
                this._frames.Clear();
            }
            this._rowLength = (width << 2) + this.RowExtra;
            this._width = Math.Max(0, width);
            this._height = Math.Max(0, height);
            this._buffer = new byte[(this._rowLength * height) + 4];
            for (int i = 0; i < this._height; i++)
            {
                int index = i * this._rowLength;
                this._buffer[index] = 0;
                byte[] bytes = BitConverter.GetBytes((ushort)(this._rowLength - 5));
                this._buffer[index + 1] = bytes[0];
                this._buffer[index + 2] = bytes[1];
                this._buffer[index + 3] = (byte)(~bytes[0]);
                this._buffer[index + 4] = (byte)(~bytes[1]);
                this._buffer[index + 5] = 0;
            }
        }

        public void SetStream(Stream stream)
        {
            switch (GetImageFormat(stream))
            {
                case ImageFormat.Png:
                    try
                    {
                        PngCodec.Decode(this, stream);
                        goto Label_0067;
                    }
                    catch (Exception)
                    {
                        throw new BadImageFormatException("PNG image could not be decoded.");
                    }
                    break;

                case ImageFormat.Jpg:
                    break;

                case ImageFormat.Gif:
                    goto Label_0047;

                default:
                    goto Label_005C;
            }
            try
            {
                JpgCodec.Decode(this, stream);
                goto Label_0067;
            }
            catch (Exception)
            {
                throw new BadImageFormatException("JPG image could not be decoded.");
            }
        Label_0047:
            try
            {
                GifCodec.Decode(this, stream);
                goto Label_0067;
            }
            catch (Exception)
            {
                throw new BadImageFormatException("GIF image could not be decoded.");
            }
        Label_005C:
            throw new BadImageFormatException("Image format not recognized.");
        Label_0067:
            this.Invalidate();
        }

        private void SetUriSource(Uri uriSource)
        {
            if (uriSource != this.UriSource)
            {
                this._uriSource = uriSource;
                if (this._uriSource == null)
                {
                    this.SetStream(null);
                }
                else
                {
                    if (((this._uriSource.IsAbsoluteUri && (this._uriSource.Scheme == "file")) && ((Application.Current != null) && Application.Current.IsRunningOutOfBrowser)) && Application.Current.HasElevatedPermissions)
                    {
                        try
                        {
                            using (FileStream stream = File.OpenRead(this._uriSource.LocalPath))
                            {
                                this.SetStream(stream);
                            }
                        }
                        catch (SecurityException)
                        {
                        }
                    }
                    if (!this._uriSource.IsAbsoluteUri)
                    {
                        Uri uriResource = this._uriSource;
                        if (!Regex.Match(uriResource.ToString(), "^/.*?;component/").Success)
                        {
                            string str = Application.Current.GetType().Assembly.FullName.Split(new char[] { ',' })[0];
                            uriResource = new Uri(string.Format("/{0};component/{1}", str, this._uriSource.ToString()), UriKind.Relative);
                        }
                        StreamResourceInfo resourceStream = Application.GetResourceStream(uriResource);
                        if (resourceStream != null)
                        {
                            this.SetStream(resourceStream.Stream);
                            return;
                        }
                    }
                    try
                    {
                        WebClient client = new WebClient();
                        client.OpenReadCompleted += new OpenReadCompletedEventHandler(this.wc_OpenReadCompleted);
                        client.OpenReadAsync(this._uriSource);
                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            if (!e.Cancelled && (e.Error == null))
            {
                this.SetStream(e.Result);
            }
        }

        // Properties
        internal AnimationFrameCollection Frames
        {
            get
            {
                if (this._frames == null)
                {
                    this._frames = new AnimationFrameCollection(this);
                }
                return this._frames;
            }
        }

        public int Height
        {
            get
            {
                return this._height;
            }
        }

        public ImageSource ImageSource
        {
            get
            {
                if (this._imgSource == null)
                {
                    this._imgSource = new BitmapImage();
                    this.Invalidate();
                }
                return this._imgSource;
            }
        }

        public InterpolationMode InterpolationMode
        {
            get
            {
                return this._interpolationMode;
            }
            set
            {
                this._interpolationMode = value;
            }
        }

        internal int RowExtra
        {
            get
            {
                return 6;
            }
        }

        public Uri UriSource
        {
            get
            {
                return this._uriSource;
            }
            set
            {
                this.SetUriSource(value);
            }
        }

        public int Width
        {
            get
            {
                return this._width;
            }
        }
    }
}
